package com.hqd.jjvm.rtda.heap;

import com.hqd.jjvm.rtda.heap.accesss.ClassAccessFlag;
import com.hqd.jjvm.rtda.heap.classloader.JClassLoader;
import com.hqd.jjvm.rtda.heap.state.JClassState;
import com.hqd.jjvm.rtda.memory.JMethodArea;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.rtda.slot.NumberSlot;
import com.hqd.jjvm.rtda.slot.RefSlot;
import com.hqd.jjvm.rtda.slot.Slot;
import com.hqd.jjvm.util.ClassUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.ArrayUtils;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

@Setter
@Getter
@AllArgsConstructor
public class JClass extends BaseMember {
    private String sourceFile;
    /**
     * 类加载状态
     */
    private JClassState state;
    /**
     * 父类名
     */
    private String superClassName;
    /**
     * 接口名
     */
    private String[] interfaceNames;
    /**
     * 运行时常量池
     */
    private RuntimeConstantPool constantPool;
    /**
     * 属性
     */
    private JField[] jFields;
    /**
     * 方法
     */
    private JMethod[] jMethods;
    /**
     * 类加载器
     */
    private JClassLoader loader;
    /**
     * 父类
     * Q:为什么接口会继承Object
     */
    private JClass superClass;
    /**
     * 属性变量数量
     */
    private Integer instanceSlotCount;
    /**
     * 实现接口
     */
    private JClass[] interfaceClasses;
    /**
     * 静态变量数量
     */
    private Integer staticSlotCount;
    /**
     * 静态变量
     */
    private Slot[] staticVars;
    private Map<String, JMethod> vtableMap;

    /**
     * 存放class对象
     */
    private JObject jObject;

    public JClass() {
        this.vtableMap = new HashMap<>();
    }


    public boolean isInterface() {
        return (getAccessFlags() & ClassAccessFlag.ACC_INTERFACE.getHex()) == 0 ? false : true;
    }

    public boolean isAbstract() {
        return (getAccessFlags() & ClassAccessFlag.ACC_ABSTRACT.getHex()) == 0 ? false : true;
    }


    public boolean isSuper() {
        return (getAccessFlags() & ClassAccessFlag.ACC_SUPER.getHex()) == 0 ? false : true;
    }

    public boolean isArray() {
        return this.getName().startsWith("[");
    }

    /**
     * 判断访问权限
     *
     * @param refJClass
     * @return
     */
    public boolean isAccessibleTo(JClass refJClass) {
        return this.isPublic() || this.getPackageName().equals(refJClass.getPackageName());
    }

    /**
     * 获取包名
     * eg:java/lang/Object 类名:Object 路径:java/lang
     *
     * @return
     */
    public String getPackageName() {
        if (getName().lastIndexOf("/") != -1) {
            return getName().substring(0, getName().lastIndexOf("/"));
        }
        return "";
    }

    public boolean isSubClassOf(JClass jClass) {
        return this.superClass == jClass;
    }

    public <T extends JObject> JObject newJArray(int size) {
        switch (this.getClassName()) {
            case "[I":
                return new JObject(this, new int[size]);
            case "[S":
                return new JObject(this, new short[size]);
            case "[Z":
                return new JObject(this, new boolean[size]);
            case "[B":
                return new JObject(this, new byte[size]);
            case "[C":
                return new JObject(this, new char[size]);
            case "[J":
                return new JObject(this, new long[size]);
            case "[F":
                return new JObject(this, new float[size]);
            case "[D":
                return new JObject(this, new double[size]);
            default:
                return new JObject(this, new JObject[size]);
        }
    }

    public JObject newJObject() {
        JObject jObject = new JObject(this, instanceSlotCount == null ? 0 : instanceSlotCount);
        initInstanceField(this, jObject);
        jObject.setJClass(this);
        return jObject;
    }

    /**
     * 初始化实例变量
     *
     * @param jClass
     * @param jObject
     */
    private void initInstanceField(JClass jClass, JObject jObject) {
        if (jClass != null) {
            initInstanceField(jClass.getSuperClass(), jObject);
            for (JField field : jClass.getJFields()) {
                if (!field.isStatic()) {
                    if (ClassUtil.isPrimitive(field.getDescriptor())) {
                        if (ClassUtil.isFloat(field.getDescriptor())) {
                            jObject.getFields()[field.getSlotId()] = new NumberSlot(0F);
                        } else if (ClassUtil.isDouble(field.getDescriptor())) {
                            jObject.getFields()[field.getSlotId()] = new NumberSlot(0D);
                        } else if (ClassUtil.isLong(field.getDescriptor())) {
                            jObject.getFields()[field.getSlotId()] = new NumberSlot(0L);
                        } else {
                            jObject.getFields()[field.getSlotId()] = new NumberSlot(0);
                        }
                    } else {
                        jObject.getFields()[field.getSlotId()] = new RefSlot(null);
                    }
                }
            }
        }
    }

    /**
     * 获取main函数
     *
     * @return
     */
    public JMethod getMainMethod() {
        for (JMethod jMethod : jMethods) {
            if (jMethod.getDescriptor().equals("([Ljava/lang/String;)V") && jMethod.getName().equals("main")) {
                return jMethod;
            }
        }
        throw new Error("找不到或无法加载主类 " + getName());
    }

    public JMethod getClinitMethod() {
        for (JMethod jMethod : jMethods) {
            if (jMethod.getName().equals("<clinit>") && jMethod.getDescriptor().equals("()V")) {
                return jMethod;
            }
        }
        return null;
    }

    private void initClinit(JClass jc, Deque<JMethod> clinitMethodQueue) {
        try {
            if (jc.getState().equals(JClassState.INITIALIZATION_ERROR)) {
                throw new Error("初始化异常");
            }
            if (!jc.getState().equals(JClassState.FULLY_INITIALIZED)) {
                jc.setState(JClassState.BEING_INITIALIZED);
                JMethod clinitMethod = jc.getClinitMethod();
                if (clinitMethod != null) {
                    clinitMethod.setJClass(jc);
                    clinitMethodQueue.add(clinitMethod);
                } else {
                    jc.setState(JClassState.FULLY_INITIALIZED);
                }
                if (jc.getSuperClass() != null) {
                    initClinit(jc.getSuperClass(), clinitMethodQueue);
                }
            }
        } catch (Throwable t) {
            jc.setState(JClassState.INITIALIZATION_ERROR);
            throw new Error(String.format("clinit error %s ", t.getMessage()));
        }
    }

    public Deque<JMethod> initClinit() {
        Deque<JMethod> clinitMethodQueue = new ArrayDeque<>();
        if (this.getSuperClass() != null) {
            if (this.getSuperClass().getState().equals(JClassState.FULLY_INITIALIZED)) {
                if (this.getJMethod("<clinit>", "()V") == null) {
                    this.setState(JClassState.FULLY_INITIALIZED);
                    JMethodArea.getJClass(this.getClassName()).setState(JClassState.FULLY_INITIALIZED);
                    return clinitMethodQueue;
                }
            }
        }
        initClinit(this, clinitMethodQueue);
        return clinitMethodQueue;
    }

    public boolean isSuperClassOf(JClass currentClass) {
        if (currentClass.getSuperClass() == null) {
            return false;
        }
        if (currentClass.getSuperClass().equals(this)) {
            return true;
        } else {
            return isSuperClassOf(currentClass.getSuperClass());
        }
    }

    public static boolean isSuperInterfaceOf(JClass jc, JClass subJC) {
        if (ArrayUtils.contains(jc.getInterfaceClasses(), subJC)) {
            return true;
        } else {
            if (jc.getSuperClass() != null) {
                return isSuperInterfaceOf(jc.getSuperClass(), subJC);
            }
            return false;
        }
    }

    public JField getJField(String name, String descriptor) {
        return getJField(this, name, descriptor);
    }

    private JField getJField(JClass jc, String name, String descriptor) {
        for (JField jField : jc.getJFields()) {
            if (jField.getName().equals(name) && jField.getDescriptor().equals(descriptor)) {
                return jField;
            }
        }
        if (jc.getSuperClass() != null) {
            return getJField(jc.getSuperClass(), name, descriptor);
        }
        return null;
    }

    public JMethod getJMethod(String name, String descriptor) {
        for (JMethod jMethod : jMethods) {
            if (jMethod.getName().equals(name) && jMethod.getDescriptor().equals(descriptor)) {
                return jMethod;
            }
        }
        return null;
    }

    public int getInstanceSlotCount() {
        return instanceSlotCount == null ? 0 : instanceSlotCount;
    }

    public String getClassName() {
        return getName();
    }

    public void setClassName(String className) {
        setName(className);
    }

    public boolean isAssignableFrom(JClass other) {
        if (this.equals(other)) {
            return true;
        }
        if (!this.isInterface()) {
            return other.isSubClassOf(this);
        } else {
            return isImplements(this, other);
        }
    }

    public static boolean isImplements(JClass jc, JClass ijc) {
        if (ArrayUtils.contains(jc.getInterfaceClasses(), ijc)) {
            return true;
        } else {
            if (jc.getSuperClass() != null) {
                return isImplements(jc.getSuperClass(), ijc);
            }
        }
        return false;
    }

    public static boolean isAssignableFrom(JObject ref, JClass jc) {
        JClass refJC = ref.getJClass();
        if (refJC.equals(jc)) {
            return true;
        }
        if (refJC.isArray()) {
            //TODO
            return true;

        } else {
            if (jc.isArray()) {
                return true;
            } else {
                if (refJC.isInterface()) {
                    if (jc.isInterface()) {
                        return isSuperInterfaceOf(jc, refJC);
                    } else {
                        return jc.getClassName().equals("java/lang/Object");
                    }
                } else {
                    if (jc.isInterface()) {
                        return isImplements(refJC, jc);
                    } else {
                        return jc.isSuperClassOf(refJC);
                    }
                }
            }
        }
    }
}
